//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.http;

import java.net.*;
import java.io.*;
import java.util.*;
import java.beans.*;

import ants.p2p.*;
import ants.p2p.security.*;

import org.apache.log4j.*;

public class HttpRequestSender extends Thread implements PropertyChangeListener{
  Socket s = null;
  InputStream is;
  OutputStream os;
  HttpProxy proxy;
  HttpRequestMessage httpRequestMessage = null;
  HttpRequestSenderWatchDog watchDog;
  ArrayList receivedParts = new ArrayList();
  int lastWrittenId = -1;

  static Logger _logger = Logger.getLogger(HttpRequestSender.class.getName());

  public HttpRequestSender(Socket s, HttpProxy proxy) {
    this.proxy = proxy;
    try{
      this.s = s;
      this.is = s.getInputStream();
      this.os = s.getOutputStream();
    }catch(Exception e){
      _logger.error("Initialization error",e);
    }
  }

  public HttpRequestMessage getHttpRequestMessage(){
    return this.httpRequestMessage;
  }

  public void run(){
    try{
      watchDog = new HttpRequestSenderWatchDog(this);
      _logger.debug("Getting request from browser...");
      ArrayList byteList = new ArrayList();
      int total = 0;
      do {
        byte[] bytes = new byte[1024];
        int n = is.read(bytes);
        total += n;
        byteList.add(bytes);
      } while (is.available() > 0);
      s.shutdownInput();

      _logger.debug("Merging request parts...");
      byte[] request = new byte[total];
      int copied = 0;
      for(int x = 0; x < byteList.size(); x++){
        byte[] currentBytesList = (byte[])byteList.get(x);
        for(int k = 0; k < currentBytesList.length && copied < total; k++, copied++){
          request[copied] = currentBytesList[k];
        }
      }

      _logger.debug("Show Browser Request:");
      String browserRequest = "";
      for (int x = 0; x < request.length; x++) {
        browserRequest += ( (char) request[x]);
      }
      _logger.debug(browserRequest);


      _logger.debug("Sending request...");
      ants.p2p.security.EndpointSecurityManager esm = this.proxy.getWarriorAnt().
          getOutputSecureConnectionManager(this.proxy.getNodeID());
      if (esm == null){
        s.close();
        throw new Exception("No secure connection avaiable with endpoint " +
                            this.proxy.getNodeID());
      }

      httpRequestMessage = new HttpRequestMessage(request);

      httpRequestMessage.encrypt(esm.getCipherEnc());
      MessageWrapper wm = this.proxy.getWarriorAnt().sendMessage(
          httpRequestMessage, this.proxy.getNodeID(), true, false);
      while (this.proxy.getWarriorAnt().myMessages.contains(wm)) {
        Thread.sleep(1000);
      }
      if (this.proxy.getWarriorAnt().failedMessages.contains(wm)) {
        httpRequestMessage.decrypt(esm.getCipherDec());
        this.proxy.getWarriorAnt().failedMessages.remove(wm);

        _logger.debug(this.proxy.getWarriorAnt().getShortId() +
                      ": Error in sending HttpRequestMessage" +
                      " - Cannot send pull request to id " +
                      httpRequestMessage.getDest());

        this.proxy.getWarriorAnt().propertyChangeSupport.firePropertyChange(
            "httpRequestError", null, httpRequestMessage);
        return;
      }
    }catch(Exception e){e.printStackTrace();}
  }

  String httpMonitor = "HTTP_MONITOR";

  public void checkAndWriteReceivedParts() throws Exception{
    synchronized (this.httpMonitor) {
      _logger.info("Checking and Writing parts...");
      while (this.receivedParts.size() != 0 &&
             ( (HttpResponsePartMessage)this.receivedParts.get(0)).getId().
             intValue() == this.lastWrittenId + 1) {
        HttpResponsePartMessage rp = (HttpResponsePartMessage)this.receivedParts.remove(0);
        boolean socketClosed = s.isOutputShutdown() || s.isClosed();
        if (!socketClosed) {
          _logger.info("HTTP connection alive! Writing parts");
          try {
            this.os.write(rp.getBody());
          }
          catch (IOException ex) {
            _logger.error("Socket closed", ex);
            socketClosed = true;
          }
          this.lastWrittenId++;
        }
        if(socketClosed) {
          _logger.info("HTTP connection closed! Aborting!");
          ants.p2p.security.EndpointSecurityManager esm = this.proxy.
              getWarriorAnt().
              getOutputSecureConnectionManager(this.proxy.getNodeID());
          if (esm == null) {
            s.close();
            throw new Exception("No secure connection avaiable with endpoint " +
                                this.proxy.getNodeID());
          }

          HttpInterruptTransferMessage httpInterruptMessage = new
              HttpInterruptTransferMessage(new Integer(0), new byte[0],
                                           "HTTPInterruptTransfer",
                                           httpRequestMessage);

          httpInterruptMessage.encrypt(esm.getCipherEnc());
          MessageWrapper wm = this.proxy.getWarriorAnt().sendMessage(
              httpInterruptMessage, this.proxy.getNodeID(), true, false);
          while (this.proxy.getWarriorAnt().myMessages.contains(wm)) {
            Thread.sleep(1000);
          }
          if (this.proxy.getWarriorAnt().failedMessages.contains(wm)) {
            httpInterruptMessage.decrypt(esm.getCipherDec());
            this.proxy.getWarriorAnt().failedMessages.remove(wm);

            _logger.debug(this.proxy.getWarriorAnt().getShortId() +
                          ": Error in sending HttpInterruptMessage" +
                          " - Cannot send pull request to id " +
                          httpInterruptMessage.getDest());

            this.proxy.getWarriorAnt().propertyChangeSupport.firePropertyChange(
                "httpInterruptError", null, httpInterruptMessage);
            return;
          }
          watchDog.stop();
          proxy.removeHandler(this);
        }
      }
    }
  }

  public void addReceivedPartToList(HttpResponsePartMessage rp){
    synchronized(this.httpMonitor){
      if(this.lastWrittenId >= rp.getId().intValue()) return;
      for (int x = 0; x < this.receivedParts.size(); x++) {
        if ( ( (HttpResponsePartMessage)this.receivedParts.get(x)).getId().
            equals(rp.getId()))
          return;
      }
      this.receivedParts.add(rp);
      Collections.sort(this.receivedParts);
    }
  }

  public void propertyChange(PropertyChangeEvent e){
    try{
      if(e.getPropertyName().equals("httpResponsePart")){
        if (e.getNewValue() instanceof HttpResponsePartMessage &&
            ((HttpResponsePartMessage) e.getNewValue()).getRequest().equals(this.getHttpRequestMessage())){
          _logger.info("Received HTTP response part: local "+s.getLocalPort() + " remote " + s.getPort());
          watchDog.trigger();
          this.addReceivedPartToList((HttpResponsePartMessage) e.getNewValue());
          this.checkAndWriteReceivedParts();
        }
      }else if(e.getPropertyName().equals("httpResponseEnd")){
        if (e.getNewValue() instanceof HttpTransferEndControlMessage &&
            ((HttpTransferEndControlMessage) e.getNewValue()).getHttpRequestMessage().equals(this.getHttpRequestMessage())){
          _logger.info("Received HTTP response end: local "+s.getLocalPort() + " remote " + s.getPort());
          this.checkAndWriteReceivedParts();
          watchDog.stop();
          proxy.removeHandler(this);
          s.shutdownOutput();
          s.close();
        }
      }
    }catch(Exception ex){
      _logger.error("Error in handling http response",ex);
    }
  }

  public static boolean testAddress(String address){
    address = address.toLowerCase();
    char[] hexChar = {
      '0', '1', '2', '3',
      '4', '5', '6', '7',
      '8', '9', 'a', 'b',
      'c', 'd', 'e', 'f'};
    if(address.length() != 16)
      return false;
    for(int x = 0; x < address.length(); x++){
      if(Arrays.binarySearch(hexChar,address.charAt(x)) < 0)
        return false;
    }
    return true;
  }
}

class HttpRequestSenderWatchDog extends Thread{
  HttpRequestSender s;
  static long timeOut = 60 * 1000;
  int minutesRemaining = 10;

  static Logger _logger = Logger.getLogger(HttpRequestSenderWatchDog.class.getName());

  public HttpRequestSenderWatchDog(HttpRequestSender s){
    this.s = s;
    this.start();
  }

  public void trigger(){
    minutesRemaining = 10;
  }

  public void run(){
    try {
      while(minutesRemaining >= 0){
        this.sleep(timeOut);
        minutesRemaining--;
      }
      s.proxy.removeHandler(s);
      s.s.shutdownOutput();
      s.s.close();
    }
    catch (Exception ex) {
    }
    _logger.info("HttpRequestSenderWatchDog stopped");
  }
}
